﻿using System;
using System.Diagnostics;
using Co9320.NamaAlert.Engine.Feeder;
using Co9320.NamaAlert.Engine.Lists;


namespace Co9320.NamaAlert.Engine.Inspector
{
	/// <summary>
	/// 放送終了検知用リストビルダ
	/// </summary>
	/// <remarks>
	/// 基本的に､ある時点において､このインスタンスの中に存在スレッドは単一である
	/// との仮定に基づいた設計｡
	/// 複数のスレッドが同一のタイミングでインスタンス中に存在する場合の結果は未定義｡
	/// また､開始検知系と同時実行した場合の結果も未定義｡
	/// </remarks>
	internal class InspectionListBuilder
	{
		/// <summary>
		/// 処理結果
		/// </summary>
		private enum ProcessResult : byte
		{
			/// <summary>
			/// N/A(未定義)
			/// </summary>
			NotAvailable = 0x00,
			/// <summary>
			/// 適正
			/// </summary>
			Collect = 0x01,

			/// <summary>
			/// オーバーに取りすぎ
			/// </summary>
			Over = 0x02,

			/// <summary>
			/// 少なく取りすぎ
			/// </summary>
			Less = 0x03
		}

		/// <summary>
		/// リスト取得結果
		/// </summary>
		private enum ListGetResult : byte
		{
			NotAvailable = 0x00,
			OK = 0x01,
		}

		/// <summary>
		/// 累積統計情報のノード
		/// </summary>
		private class AnalyzerNode
		{
			public AnalyzerNode()
			{
				this.Next = null;
				this.Result = ProcessResult.NotAvailable;
			}

			public AnalyzerNode Next
			{
				get;
				set;
			}

			public ProcessResult Result
			{
				get;
				set;
			}
		}

		/// <summary>
		/// 累積統計情報用コレクション
		/// </summary>
		private class Analyzer
		{
			private AnalyzerNode Current_;

			public Analyzer(int SampleSize)
			{
				Debug.Assert(SampleSize > 1);

				AnalyzerNode[] lst = new AnalyzerNode[SampleSize];

				for (int i = 0; i < lst.Length; i++)
				{
					lst[i] = new AnalyzerNode();
				}

				for (int i = 0; i < lst.Length - 1; i++)
				{
					lst[i].Next = lst[i + 1];
				}

				lst[lst.Length - 1].Next = lst[0];

				this.Current_ = lst[0];
			}

			public void SetResult(ProcessResult Result)
			{
				Debug.Assert(Result != ProcessResult.NotAvailable);

				Current_.Result = Result;
				Current_ = Current_.Next;
			}

			public void Clear()
			{
				AnalyzerNode Cursor = Current_;
				AnalyzerNode Pivot = Cursor;

				for (; ; )
				{
					Cursor.Result = ProcessResult.NotAvailable;
					Cursor = Cursor.Next;

					if (object.ReferenceEquals(Pivot, Cursor))
					{
						break;
					}
				}
			}

			public double GetResultRatio(ProcessResult Result)
			{
				Debug.Assert(Result != ProcessResult.NotAvailable);

				int Total = 0;
				int Target = 0;

				AnalyzerNode Cursor = Current_;
				AnalyzerNode Pivot = Cursor;

				for (; ; )
				{
					Total++;

					if (Cursor.Result == Result)
					{
						Target++;
					}

					Cursor = Cursor.Next;
					if (object.ReferenceEquals(Cursor, Pivot))
					{
						break;
					}
				}

				if (Target == 0)
				{
					return 0;
				}
				else
				{
					return (double)Target / (double)Total;
				}
			}


		}

		private struct Pair<T, V>
		{
			public Pair(T t, V v)
			{
				this.t = t;
				this.v = v;
			}

			public T t;
			public V v;
		}

		/// <summary>
		/// 基準放送時間
		/// </summary>
		private static readonly TimeSpan DefaultLiveLength = new TimeSpan(0, 30, 0);

		/// <summary>
		/// 超過と判断しないオーバーラップの限界
		/// </summary>
		private static readonly TimeSpan AcceptableOverlap = new TimeSpan(0, 5, 0);

		/// <summary>
		/// 必須としているオーバーラップ
		/// </summary>
		private static readonly TimeSpan RequiredOverlap = new TimeSpan(0, 0, 30);

		/// <summary>
		/// OverFeedの統計情報の結果､実際に読み取り位置を変更する閾値
		/// </summary>
		private static readonly double RatioThreshold = 0.3;


		/// <summary>
		/// 1ページ当たりのプログラム数(最大)
		/// </summary>
		private int DefaultProgramDatasPerPage_;

		/// <summary>
		/// Rssフィードエンジン
		/// </summary>
		private NicoLiveLiveInfoFeeder Feeder_;


		/// <summary>
		/// 照査対象となるリスト(オンエア検知と共有)
		/// </summary>
		private InternalLiveList List_;

		/// <summary>
		/// サンプリングアナライザ
		/// </summary>
		private Analyzer Analyzer_;

		/// <summary>
		/// Ctor
		/// </summary>
		/// <param name="LookBehindAtLeast">自動調停したページから何ページ前から読み出すか</param>
		/// <param name="List">照査対象リスト</param>
		/// <param name="Counter">フィードカウンタ</param>
		/// <param name="BaseUri">べーすURI</param>
		/// <param name="UserAgent">ユーザーエージェント</param>
		public InspectionListBuilder(int LookBehindAtLeast, InternalLiveList List,
			 NicoLiveLiveInfoFeeder feeder)
		{
			Debug.Assert(LookBehindAtLeast >= 0);
			Debug.Assert(List != null);
			Debug.Assert(feeder != null);

			Feeder_ = feeder;
			List_ = List;
			Analyzer_ = new Analyzer(10);

			this.LookBehindAtLeast = LookBehindAtLeast;
			this.CurrentLookBehind = this.LookBehindAtLeast;
			this.DefaultProgramDatasPerPage_ = 0;
		}


		/// <summary>
		/// 終了検知用ルーチンワーク
		/// </summary>
		/// <returns>終了検知数</returns>
		public int Routine()
		{
			if (List_.Count == 0)
			{
				return 0;
			}

			InspectionList lst = new InspectionList();

			DateTime Require = List_.CommunityFastestTime - DefaultLiveLength + RequiredOverlap;
			DateTime Threshold = List_.CommunityFastestTime - DefaultLiveLength + AcceptableOverlap;




			Debug.Assert(lst != null);

			if (!BuildChannelAndOfficialList(lst))
			{
				return 0;
			}


			//Set Read in page.

			int ReadStart;

			if (Feeder_.SizePerPage == 0)
			{
				return 0;
			}
			else if (Feeder_.SizePerPage != Define.InvalidValue && Feeder_.CurrentProgramCount != Define.InvalidValue)
			{
				ReadStart = Feeder_.CurrentProgramCount / Feeder_.SizePerPage;
				ReadStart += Feeder_.CurrentProgramCount % Feeder_.SizePerPage == 0 ? 0 : 1;
				ReadStart -= CurrentLookBehind;
			}
			else
			{
				return 0;
			}



			if (ReadStart < 1)
			{
				ReadStart = 1;
			}


			bool IsRetry = false;

			for (int i=0;i<Define.ExternalRetryLimit ;i++)
			{
				if (ReadStart < 1)
				{
					break;
				}

				var Result = BuildCommunityList(ReadStart, IsRetry, Require, Threshold, lst);

				if (Result == ListGetResult.OK)
				{
					break;
				}
				else
				{
					ReadStart--;
					IsRetry = true;
				}

			}




			if (Analyzer_.GetResultRatio(ProcessResult.Less) > RatioThreshold)
			{
				CurrentLookBehind++;
				Analyzer_.Clear();
			}

			if (Analyzer_.GetResultRatio(ProcessResult.Over) > RatioThreshold)
			{
				if (CurrentLookBehind - 1 > LookBehindAtLeast)
				{
					CurrentLookBehind--;
				}
				Analyzer_.Clear();
			}



			List_.Inspect(lst);


			return Feeder_.CurrentProgramCount;
		}

		private bool BuildChannelAndOfficialList(InspectionList List)
		{
			Feeder_.ResetPage();


			for (int Count=0; ;Count++ )
			{
				var resulst = Feeder_.ReadPage();

				this.DefaultProgramDatasPerPage_ = resulst.Count;

				for (int i = 0; i < resulst.Count; i++)
				{
					if (resulst[i].Station.Kind != Co9320.NamaAlert.Model.StationKind.Community)
					{
						List.Add(resulst[i]);
					}
				}

				if (resulst.Count == 0&& Count==0)
				{
					return false;
				}
				else if (resulst.HasCommunity || resulst.Count==0)
				{
					break;
				}
				else
				{
					continue;
				}
			}

			return true;
		}


		private ListGetResult BuildCommunityList(int ReadStartPage, bool IsRetry, DateTime Require, DateTime Threshold, InspectionList inspectionList)
		{
			Feeder_.ResetPage(ReadStartPage);

			var lst = Feeder_.ReadPage();
			ProcessResult Result = ProcessResult.NotAvailable;

			if (!lst.HasValue)
			{
				return ListGetResult.NotAvailable;
			}
			else
			{
				if (lst.CommunityLiveFastest < Require)
				{
					Analyzer_.SetResult(ProcessResult.Less);
					return ListGetResult.NotAvailable;
				}
				else
				{
					int EndCount = 0;

					if (lst.CommunityLiveFastest < Threshold)
					{
						if (IsRetry)
						{
							Result = ProcessResult.Collect;
						}
						else
						{
							Result = ProcessResult.Over;
						}
					}
					else
					{
						Result = ProcessResult.Collect;
					}

					int i = 0;


					if (lst.Count != DefaultProgramDatasPerPage_)
					{
						EndCount++;
					}

					inspectionList.Add(lst);

					for (i = 0; ; i++)
					{
						lst = Feeder_.ReadPage();
						if (lst.HasValue)
						{
							inspectionList.Add(lst);

							if (lst.Count != DefaultProgramDatasPerPage_)
							{
								EndCount++;
							}

						}
						else
						{
							break;
						}
					}


					if (i == 0)
					{
						Result = ProcessResult.Collect;
					}

					Analyzer_.SetResult(Result);

					return ListGetResult.OK;
				}
			}
		}

		public int CurrentLookBehind
		{
			get;
			private set;
		}

		public int LookBehindAtLeast
		{
			get;
			private set;
		}
	}
}
